"""
The MIT License (MIT)
Copyright © 2020 Walkline Wang (https://walkline.wang)
https://gitee.com/walkline/esp32-ble
"""
import ubluetooth as bt
from ble.profile.manager import ProfileManager
from ble.services import *
from ble.characteristics import *
from ble.descriptors import *
from ble.const import BLEConst
from ble.tools import BLETools
import struct
import gc

PACK = struct.pack

# http://www.freebsddiary.org/APC/usb_hid_usages
REPORT_MAP_DATA = [
	0x05, 0x01, # Usage Page (Generic Desktop)
	0x09, 0x02, # Usage (Mouse)
	0xA1, 0x01, # Collection (Application)
	0x85, 0x01, #	Report Id (1)
	0x09, 0x01, #	Usage (Pointer)
	0xA1, 0x00, #	Collection (Physical)
	0x05, 0x09, #		Usage Page (Buttons)
	0x19, 0x01, #		Usage Minimum (01) - Button 1
	0x29, 0x03, #		Usage Maximum (03) - Button 3
	0x15, 0x00, #		Logical Minimum (0)
	0x25, 0x01, #		Logical Maximum (1)
	0x75, 0x01, #		Report Size (1)
	0x95, 0x03, #		Report Count (3)
	0x81, 0x02, #		Input (Data, Variable, Absolute) - Button states
	0x75, 0x05, #		Report Size (5)
	0x95, 0x01, #		Report Count (1)
	0x81, 0x01, #		Input (Constant) - Padding or Reserved bits
	0x05, 0x01, #		Usage Page (Generic Desktop)
	0x09, 0x30, #		Usage (X)
	0x09, 0x31, #		Usage (Y)
	0x09, 0x38, #		Usage (Wheel)
	0x15, 0x81, #		Logical Minimum (-127)
	0x25, 0x7F, #		Logical Maximum (127)
	0x75, 0x08, #		Report Size (8)
	0x95, 0x03, #		Report Count (3)
	0x81, 0x06, #		Input (Data, Variable, Relative) - X & Y coordinate
	0xC0,       #	End Collection
	0xC0,       # End Collection

	0x05, 0x01, # USAGE_PAGE (Generic Desktop)
	0x09, 0x06, # USAGE (Keyboard)
	0xA1, 0x01, # COLLECTION (Application)

	# 8 keycode 控制键键位
	0x85, 0x02, #	Report Id (2)
	0x05, 0x07, #	USAGE_PAGE (Keyboard)
	0x19, 0xE0, # 	USAGE_MINIMUM (Keyboard LeftControl)
	0x29, 0xE7, # 	USAGE_MAXIMUM (Keyboard Right GUI)
	# keycode value 0 and 1
	0x15, 0x00, # 	LOGICAL_MINIMUM (0)
	0x25, 0x01, # 	LOGICAL_MAXIMUM (1)
	# Modifier byte
	0x75, 0x01, # 	REPORT_SIZE (1)
	0x95, 0x08, # 	REPORT_COUNT (8)
	0x81, 0x02, # 	INPUT (Data,Var,Abs)

	# Reserved byte 保留键位
	0x95, 0x01, # 	REPORT_COUNT (1)
	0x75, 0x08, # 	REPORT_SIZE (8)
	0x81, 0x01, # 	INPUT (Cnst,Var,Abs)

	# Key arrays (6 bytes) 常规键键位
	0x05, 0x07, # 	USAGE_PAGE (Keyboard)
	0x19, 0x00, # 	USAGE_MINIMUM (Reserved (no event indicated))
	0x29, 0xA4, # 	USAGE_MAXIMUM (Keyboard ExSel)
	0x15, 0x00, # 	LOGICAL_MINIMUM (0)
	0x25, 0xA4, # 	LOGICAL_MAXIMUM (164)
	0x95, 0x06, # 	REPORT_COUNT (6)
	0x75, 0x08, # 	REPORT_SIZE (8)
	0x81, 0x00, # 	INPUT (Data,Ary,Abs)

	# # LED report 不要被动灯光控制
	# 0x05, 0x08, # 	USAGE_PAGE (LEDs)
	# 0x19, 0x01, # 	USAGE_MINIMUM (Num Lock)
	# 0x29, 0x05, # 	USAGE_MAXIMUM (Kana)
	# 0x95, 0x05, # 	REPORT_COUNT (5)
	# 0x75, 0x01, # 	REPORT_SIZE (1)
	# 0x91, 0x02, # 	OUTPUT (Data,Var,Abs)

	# # LED report padding 补位
	# 0x95, 0x01, # 	REPORT_COUNT (1)
	# 0x75, 0x03, # 	REPORT_SIZE (3)
	# 0x91, 0x03, # 	OUTPUT (Cnst,Var,Abs)
	0xC0,		# END_COLLECTION
]

class GenericAccessValues(object):
	APPEARANCE = PACK("<h", BLEConst.Appearance.KEYBOARD)
	PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = PACK("<4h", 40, 80, 10, 300) # (6, 6, 60, 300) <- ble mouse # (min, max, latency, timeout), for min & max, 1 = 1.25ms
	CENTRAL_ADDRESS_RESOLUTION = PACK("<b", 1)


class BatteryServiceValues(object):
	BATTERY_LEVEL_INT = PACK("<B", int(100))


class DeviceInformationValues(object):
	MANUFACTURER_NAME_STRING = PACK("<17s", "Walkline Hardware".encode())
	MODEL_NUMBER_STRING = PACK("<11s", "WKBP-B20".encode())
	SERIAL_NUMBER_STRING = PACK("<36s", "4e897424-061d-4dd9-a798-454f79b37245".encode())
	FIRMWARE_REVISION_STRING = PACK("<4s", "v0.1".encode())
	HARDWARE_REVISION_STRING = PACK("<4s", "v0.2.1".encode())
	SOFTWARE_REVISION_STRING = PACK("<4s", "v0.1".encode())

	# key="1" value="Bluetooth SIG assigned Company Identifier value from the Assigned Numbers document"
	# key="2" value="USB Implementer’s Forum assigned Vendor ID value"
	# VendorID: https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
	PNP_ID = PACK("<BHHH", 1, 0x02E5, 0x01, 0x01) # 0x02E5: Espressif, 0x0006: Microsoft


class HumanInterfaceDeviceValues(object):
	# key="0" value="Boot Protocol Mode"
	# key="1" value="Report Protocol Mode"
	PROTOCOL_MODE = PACK("<B", int(1))
	HID_INFORMATION = PACK("<H2b", 0x0111, 0x00, 0b0011)
	# param 1: Report ID
	# param 2: Report Type
	# value="Input Report" key="1"
	# value="Output report" key="2"
	# value="Feature Report" key="3"
	REPORT_REFERANCE_MOUSE = PACK("<BB", int(1), int(1))
	REPORT_REFERANCE_KEYBOARD = PACK("<BB", int(2), int(1))


class BLE_HID(object):
	def __init__(self, ble, name='Walkline KPB20'):
		self.__ble = ble
		self.__device_name = name
		self.__conn_handle = None

		self.__profile_manager_adv = ProfileManager()
		self.__profile_manager_resp = ProfileManager()

		self.__write = self.__ble.gatts_write
		self.__read = self.__ble.gatts_read
		self.__notify = self.__ble.gatts_notify

		self.__ble.active(False)
		print("activating ble...")
		self.__ble.active(True)
		print("ble activated")

		self.__ble.config(rxbuf=256)
		print("mac: [{}]".format(BLETools.decode_mac(self.__ble.config("mac"))))
		print("rxbuf: [{}]".format(self.__ble.config("rxbuf")))

		self.__profile_manager_adv.add_services(
			BatteryService().add_characteristics(
				Characteristics(BLEConst.Characteristics.BATTERY_LEVEL, BLEConst.CharacteristicFlags.BATTERY_LEVEL_),
			),
			DeviceInformation().add_characteristics(
				Characteristics(BLEConst.Characteristics.MODEL_NUMBER_STRING, BLEConst.CharacteristicFlags.MODEL_NUMBER_STRING_),
				Characteristics(BLEConst.Characteristics.SERIAL_NUMBER_STRING, BLEConst.CharacteristicFlags.SERIAL_NUMBER_STRING_),
				Characteristics(BLEConst.Characteristics.FIRMWARE_REVISION_STRING, BLEConst.CharacteristicFlags.FIRMWARE_REVISION_STRING_),
				Characteristics(BLEConst.Characteristics.HARDWARE_REVISION_STRING, BLEConst.CharacteristicFlags.HARDWARE_REVISION_STRING_),
				Characteristics(BLEConst.Characteristics.SOFTWARE_REVISION_STRING, BLEConst.CharacteristicFlags.SOFTWARE_REVISION_STRING_),
				Characteristics(BLEConst.Characteristics.MANUFACTURER_NAME_STRING, BLEConst.CharacteristicFlags.MANUFACTURER_NAME_STRING_),
				Characteristics(BLEConst.Characteristics.PNP_ID, BLEConst.CharacteristicFlags.PNP_ID_),
			),
			HumanInterfaceDevice().add_characteristics(
				Characteristics(BLEConst.Characteristics.PROTOCOL_MODE, BLEConst.CharacteristicFlags.PROTOCOL_MODE_),
				Characteristics(BLEConst.Characteristics.REPORT, BLEConst.CharacteristicFlags.REPORT_).add_descriptors(
					# ReportReference(),
					Descriptor(bt.UUID(BLEConst.Descriptors.REPORT_REFERENCE), bt.FLAG_READ | bt.FLAG_WRITE | bt.FLAG_NOTIFY),
				),
				Characteristics(BLEConst.Characteristics.REPORT, BLEConst.CharacteristicFlags.REPORT_).add_descriptors(
					# ReportReference(),
					Descriptor(bt.UUID(BLEConst.Descriptors.REPORT_REFERENCE), bt.FLAG_READ | bt.FLAG_WRITE | bt.FLAG_NOTIFY),
				),
				Characteristics(BLEConst.Characteristics.REPORT_MAP, BLEConst.CharacteristicFlags.REPORT_MAP_),
				Characteristics(BLEConst.Characteristics.BOOT_KEYBOARD_INPUT_REPORT, BLEConst.CharacteristicFlags.BOOT_KEYBOARD_INPUT_REPORT_),
				Characteristics(BLEConst.Characteristics.BOOT_KEYBOARD_OUTPUT_REPORT, BLEConst.CharacteristicFlags.BOOT_KEYBOARD_OUTPUT_REPORT_),
				Characteristics(BLEConst.Characteristics.HID_INFORMATION, BLEConst.CharacteristicFlags.HID_INFORMATION_),
				Characteristics(BLEConst.Characteristics.HID_CONTROL_POINT, BLEConst.CharacteristicFlags.HID_CONTROL_POINT_),
			),
		)

		self.__profile_manager_resp.add_services(
			GenericAccess().add_characteristics(
				Characteristics(BLEConst.Characteristics.DEVICE_NAME, BLEConst.CharacteristicFlags.DEVICE_NAME_),
				Characteristics(BLEConst.Characteristics.APPEARANCE, BLEConst.CharacteristicFlags.APPEARANCE_),
				# Characteristics(BLEConst.Characteristics.PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS, BLEConst.CharacteristicFlags.PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS_),
				Characteristics(BLEConst.Characteristics.CENTRAL_ADDRESS_RESOLUTION, BLEConst.CharacteristicFlags.CENTRAL_ADDRESS_RESOLUTION_),
			),
			GenericAttribute().add_characteristics(
				Characteristics(BLEConst.Characteristics.SERVICE_CHANGED, BLEConst.CharacteristicFlags.SERVICE_CHANGED_),
			),
		)

		self.__services = self.__profile_manager_adv.get_services() + self.__profile_manager_resp.get_services()

		self.__ble.irq(handler=self.__irq)
		self.__register_services()

		self.__adv_payload = self.advertising_hid_payload(
			services=self.__profile_manager_adv.get_services_uuid(),
			appearance=BLEConst.Appearance.KEYBOARD,
			# tx_power=-57
		)
		self.__resp_payload = BLETools.advertising_resp_payload(
			services=self.__profile_manager_resp.get_services_uuid(),
			name=self.__device_name
		)

		self.__advertise()

	def advertising_hid_payload(self, services=None, appearance=0, tx_power=None):
		"""
		Generate paylaod for HID device
		"""
		payload = bytearray()

		def _append(adv_type, value):
			nonlocal payload
			payload += PACK('BB', len(value) + 1, adv_type) + value

		_append(BLEConst.ADType.AD_TYPE_FLAGS, PACK('B', 0x06))

		if services:
			for uuid in services:
				b = bytes(uuid)
				if len(b) == 2:
					_append(BLEConst.ADType.AD_TYPE_16BIT_SERVICE_UUID_COMPLETE, b)
				elif len(b) == 4:
					_append(BLEConst.ADType.AD_TYPE_32BIT_SERVICE_UUID_COMPLETE, b)
				elif len(b) == 16:
					_append(BLEConst.ADType.AD_TYPE_128BIT_SERVICE_UUID_COMPLETE, b)

		_append(BLEConst.ADType.AD_TYPE_APPEARANCE, PACK('<h', appearance))
		# _append(BLEConst.ADType.AD_TYPE_MANUFACTURER_SPECIFIC_DATA, PACK('<h3b', 0x0006, 0x03, 0x00, 0x80)) # 0x004c, 0x02, 0x00, 0x80))

		if tx_power:
			_append(BLEConst.ADType.AD_TYPE_TX_POWER_LEVEL, PACK('<h', BLETools.convert_tx_power_level(tx_power)))

		return payload

	def __advertise(self, interval_us=100000):
		self.__ble.gap_advertise(None)
		self.__ble.gap_advertise(interval_us, adv_data=self.__adv_payload, resp_data=self.__resp_payload)
		print("advertising...")

	def __register_services(self):
		(
			(
				self.__handle_battery_level,
			),
			(
				self.__handle_model,
				self.__handle_serial,
				self.__handle_firmware,
				self.__handle_hardware,
				self.__handle_software,
				self.__handle_manufacturer,
				self.__handle_pnp_id,
			),
			(
				self.__handle_protocol_mode,
				self.__handle_report_mouse,
				self.__handle_report_report_reference_mouse_descriptor,
				self.__handle_report_keyboard,
				self.__handle_report_report_reference_keyboard_descriptor,
				self.__handle_report_map,
				self.__handle_boot_kb_input_report,
				self.__handle_boot_kb_output_report,
				self.__handle_hid_information,
				self.__handle_hid_control_point,
				# self.handle___boot_kb_input_report_descriptor,
			),
			(
				self.__handle_device_name,
				self.__handle_appearance,
				# self.__handle_peripheral_preferred_connection_parameters,
				self.__handle_central_address_resolution,
			),
			(
				self.__handle_service_changed,
			),
		) = self.__ble.gatts_register_services(self.__services)
		
		print("services registed")

		self.__setup_generic_access()
		# self.set_battery_level()
		self.__setup_device_info()
		self.__setup_hid()
		print("characteristic values setup")

	def __irq(self, event, data):
		if event == BLEConst.IRQ.IRQ_CENTRAL_CONNECT:
			self.__conn_handle, addr_type, addr, = data
			print("[{}] connected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))

			self.__ble.gap_advertise(None)
			self.__ble.start_pairing(self.__conn_handle)
		elif event == BLEConst.IRQ.IRQ_CENTRAL_DISCONNECT:
			self.__conn_handle, _, addr, = data
			print("[{}] disconnected, handle: {}".format(BLETools.decode_mac(addr), self.__conn_handle))

			self.__conn_handle = None
			self.__advertise()
		else:
			print("event: {}, data: {}".format(event, data))
		
		gc.collect()

	def __setup_generic_access(self):
		self.__write(self.__handle_device_name, PACK("<{}s".format(len(self.__device_name)), self.__device_name.encode()))
		self.__write(self.__handle_appearance, GenericAccessValues.APPEARANCE)
		# self.__write(self.__handle_peripheral_preferred_connection_parameters, GenericAccessValues.PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS)
		self.__write(self.__handle_central_address_resolution, GenericAccessValues.CENTRAL_ADDRESS_RESOLUTION)

	def __setup_device_info(self):
		"""
		used for Device Information
		"""
		self.__write(self.__handle_manufacturer, DeviceInformationValues.MANUFACTURER_NAME_STRING)
		self.__write(self.__handle_model, DeviceInformationValues.MODEL_NUMBER_STRING)
		self.__write(self.__handle_serial, DeviceInformationValues.SERIAL_NUMBER_STRING)
		self.__write(self.__handle_firmware, DeviceInformationValues.FIRMWARE_REVISION_STRING)
		self.__write(self.__handle_hardware, DeviceInformationValues.HARDWARE_REVISION_STRING)
		self.__write(self.__handle_software, DeviceInformationValues.SOFTWARE_REVISION_STRING)
		self.__write(self.__handle_pnp_id, DeviceInformationValues.PNP_ID)

	def __setup_hid(self):
		self.__write(self.__handle_protocol_mode, HumanInterfaceDeviceValues.PROTOCOL_MODE)
		self.__write(self.__handle_report_report_reference_mouse_descriptor, HumanInterfaceDeviceValues.REPORT_REFERANCE_MOUSE)
		self.__write(self.__handle_report_report_reference_keyboard_descriptor, HumanInterfaceDeviceValues.REPORT_REFERANCE_KEYBOARD)
		self.__write(self.__handle_hid_information, HumanInterfaceDeviceValues.HID_INFORMATION)
		self.__write(self.__handle_report_map, bytes(REPORT_MAP_DATA))

	def update_battery_level(self, value):
		self.__write(self.__handle_battery_level, BatteryServiceValues.BATTERY_LEVEL_INT)

		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_battery_level)

	def send_key(self, keycode):
		self.__write(self.__handle_report_keyboard, bytes([0x0, 0x0, keycode, 0x0, 0x0, 0x0, 0x0, 0x0]))
		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_report_keyboard)

		self.__write(self.__handle_report_keyboard, bytes([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]))
		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_report_keyboard)

	def send_key_down(self, keycodes, modifier):
		self.__write(self.__handle_report_keyboard, bytes([0x2, modifier , 0x0] + keycodes))
		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_report_keyboard)

	def send_key_up(self, keycodes, modifier):
		self.__write(self.__handle_report_keyboard, bytes([0x2, modifier, 0x0] + keycodes))
		if self.__conn_handle is not None:
			self.__notify(self.__conn_handle, self.__handle_report_keyboard)


def main():
	ble = bt.BLE()
	hid = BLE_HID(ble)


if __name__ == "__main__":
	main()
